package com.jlpay.open.jlpay.sdk.java.service;

import com.jlpay.open.jlpay.sdk.java.common.crypto.CryptoHelper;
import com.jlpay.open.jlpay.sdk.java.config.OrgConfig;
import com.jlpay.open.jlpay.sdk.java.enums.BaseResponseCode;
import com.jlpay.open.jlpay.sdk.java.enums.CryptoAlgorithm;
import com.jlpay.open.jlpay.sdk.java.exception.HttpExecutionException;
import com.jlpay.open.jlpay.sdk.java.exception.HttpStatusCodeException;
import com.jlpay.open.jlpay.sdk.java.exception.KeyInitializationException;
import com.jlpay.open.jlpay.sdk.java.exception.SignVerifyException;
import com.jlpay.open.jlpay.sdk.java.http.*;
import com.jlpay.open.jlpay.sdk.java.model.BaseResponse;
import com.jlpay.open.jlpay.sdk.java.model.OrgBaseReq;
import com.jlpay.open.jlpay.sdk.java.utils.HttpUtils;
import com.jlpay.open.jlpay.sdk.java.utils.StringUtils;
import com.jlpay.open.jlpay.sdk.java.utils.gm.KeyGenerateUtils;
import com.jlpay.open.jlpay.sdk.java.utils.json.JsonUtils;
import okhttp3.HttpUrl;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 嘉联支付清算服务
 *
 * @author zhaomeixia
 * @since 2024/2/20
 */
public class JlpayOrgService {

    private final HttpClient httpClient;

    private final OrgConfig config;

    private JlpayOrgService(HttpClient httpClient, OrgConfig config) {
        this.httpClient = Objects.requireNonNull(httpClient, "httpClient is required");
        this.config = Objects.requireNonNull(config, "config is required");
    }

    /**
     * 发送 POST 请求
     *
     * @param request 机构请求参数
     * @param responseClass 返回参数类型
     * @param <T> 返回参数类型
     * @return 响应对象
     * @throws IllegalArgumentException 请求参数错误
     * @throws HttpExecutionException 请求发送失败
     * @throws HttpStatusCodeException 响应状态码不在200~300之间
     * @throws KeyInitializationException 密钥初始化失败
     * @throws SignVerifyException 签名/验签失败
     */
    public <T> T post(OrgBaseReq request, Class<T> responseClass) {
        return execute(request, responseClass, HttpMethod.POST);
    }

    public <T> T get(OrgBaseReq request, Class<T> responseClass) {
        return execute(request, responseClass, HttpMethod.GET);
    }

    public <T> T execute(OrgBaseReq request, Class<T> responseClass, HttpMethod method) {
        Objects.requireNonNull(request, "request is required");
        if (Boolean.TRUE.equals(config.getVerifyOrgNo())) {
            Objects.requireNonNull(request.getOrgNo(), "orgNo is required");
        }
        if (Boolean.TRUE.equals(config.getVerifyMerchNo())) {
            Objects.requireNonNull(request.getMerchNo(), "merchNo is required");
        }

        Map<String, String> headers = buildHeaders(request);

        handleEncrypt(request, headers);

        String body = JsonUtils.toString(request);
        HttpRequest httpRequest = HttpRequest.builder()
                .url(HttpUtils.parseUrl(config.getUrl() + request.path()))
                .headers(headers)
                .body(body)
                .build();

        HttpResponse httpResponse = null;
        if (method == HttpMethod.POST) {
            httpResponse = httpClient.post(httpRequest);
        } else if (method == HttpMethod.GET) {
            httpResponse = httpClient.get(toGetRequest(httpRequest));
        }
        T response = JsonUtils.parseObject(httpResponse.getBody(), responseClass);

        handleDecrypt(response, httpResponse.getHeaders());

        return response;
    }

    public HttpResponse post(OrgBaseReq request) {
        Objects.requireNonNull(request, "request is required");
        if (Boolean.TRUE.equals(config.getVerifyOrgNo())) {
            Objects.requireNonNull(request.getOrgNo(), "orgNo is required");
        }
        if (Boolean.TRUE.equals(config.getVerifyMerchNo())) {
            Objects.requireNonNull(request.getMerchNo(), "merchNo is required");
        }

        Map<String, String> headers = buildHeaders(request);

        String body = JsonUtils.toString(request);
        HttpRequest postRequest = HttpRequest.builder()
                .url(HttpUtils.parseUrl(config.getUrl() + request.path()))
                .headers(headers)
                .body(body)
                .build();

        return httpClient.post(postRequest);
    }

    private void handleEncrypt(OrgBaseReq request, Map<String, String> headers) {
        boolean enableAutoEncrypt = Boolean.TRUE.equals(config.getAutoEncrypt());
        if (!enableAutoEncrypt) {
            return;
        }
        boolean support =
                CryptoAlgorithm.SM2_WITH_SM4.getCode().equals(config.getCryptoAlg()) && CryptoHelper.requireEncrypt(
                        request);
        if (!support) {
            return;
        }
        String key = KeyGenerateUtils.generateSm4Key(32 * 4);
        CryptoHelper.encryptWithSm4(request, key);
        String encryptedKey = CryptoHelper.encryptWithSm2(key, config.getJlpayPubKey());
        headers.put(JlpayHttpHeaders.Crypto.CRYPTO_KEY, encryptedKey);
    }

    private void handleDecrypt(Object response, Map<String, String> headers) {
        boolean enableAutoDecrypt = Boolean.TRUE.equals(config.getAutoDecrypt());
        if (!enableAutoDecrypt) {
            return;
        }
        String cryptoAlg = headers.get(JlpayHttpHeaders.Crypto.CRYPTO_ALG);
        boolean support =
                CryptoAlgorithm.SM2_WITH_SM4.getCode().equals(cryptoAlg) && CryptoHelper.requireDecrypt(response);
        if (!support) {
            return;
        }
        if (response instanceof BaseResponse) {
            BaseResponse baseResponse = (BaseResponse) response;
            if (!BaseResponseCode.SUCCESS_V5.getRetCode().equals(baseResponse.getRetCode())) {
                return;
            }
        }
        String encryptedKey = headers.get(JlpayHttpHeaders.Crypto.CRYPTO_KEY);
        if (StringUtils.isBlank(encryptedKey)) {
            return;
        }
        String key = CryptoHelper.decryptWithSm2(encryptedKey, config.getOrgPriKey());
        CryptoHelper.decryptWithSm4(response, key);
    }

    private Map<String, String> buildHeaders(OrgBaseReq request) {
        Map<String, String> headers = new HashMap<>();
        headers.put(JlpayHttpHeaders.CONTENT_TYPE, JlpayHttpHeaders.MediaType.APPLICATION_JSON_UTF8);
        headers.put(JlpayHttpHeaders.ACCEPT, JlpayHttpHeaders.MediaType.APPLICATION_JSON_UTF8);
        headers.put(JlpayHttpHeaders.V5.APP_ID, config.getAppId());
        if (Boolean.TRUE.equals(config.getVerifyMerchNo())) {
            headers.put(JlpayHttpHeaders.V5.MERCH_NO, request.getMerchNo());
        }
        if (Boolean.TRUE.equals(config.getVerifyOrgNo())) {
            headers.put(JlpayHttpHeaders.V5.ORG_NO, request.getOrgNo());
        }
        if (StringUtils.isNotBlank(config.getCryptoAlg())) {
            headers.put(JlpayHttpHeaders.Crypto.CRYPTO_ALG, config.getCryptoAlg());
        }
        if (StringUtils.isNotBlank(request.getCryptoKey())) {
            headers.put(JlpayHttpHeaders.Crypto.CRYPTO_KEY, request.getCryptoKey());
        }
        return headers;
    }

    private HttpRequest toGetRequest(HttpRequest httpRequest) {
        // 解析请求体为Map
        Map<String, Object> bodyMap = JsonUtils.parseObject(httpRequest.getBody(), Map.class);
        final URL originalUrl = httpRequest.getUrl();

        // 使用HttpUrl正确解析原URL，包含端口和路径等完整信息
        HttpUrl httpUrl = HttpUrl.get(originalUrl);
        HttpUrl.Builder urlBuilder = httpUrl.newBuilder();

        if (bodyMap != null) {
            // 使用自动URL编码的方式添加查询参数
            bodyMap.forEach((key, value) -> {
                if (value instanceof String) {
                    urlBuilder.addQueryParameter(key, (String) value);
                }
            });
        }
        return HttpRequest.builder()
                .url(urlBuilder.build().url()) // 转换为目标URL类型
                .headers(httpRequest.getHeaders())
                .build();
    }

    public static class Builder {

        private HttpClient httpClient;

        private OrgConfig config;

        public Builder config(OrgConfig config) {
            this.config = config;
            return this;
        }

        public Builder httpClient(HttpClient httpClient) {
            this.httpClient = httpClient;
            return this;
        }

        public JlpayOrgService build() {
            Objects.requireNonNull(config, "config is required");
            if (httpClient == null) {
                httpClient = new DefaultHttpClientBuilder()
                        .config(config)
                        .build();
            }
            return new JlpayOrgService(httpClient, config);
        }
    }
}
